﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using XmlStudio.Interfaces;
using System.Xml;

namespace XmlStudio.DataLayer {
    /// <summary>
    /// Represents a xml document. This class manipulates with document, expanding, collapsing, etc.
    /// </summary>
    internal class BigXmlDocument : IBigXmlDocument {
        #region Fields
        private string path = string.Empty;
        private BigXmlReader reader;
        private BigXmlNodeInternal root;
        private bool rootRemoved = false;
        #endregion

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="BigXmlDocument"/> class.
        /// </summary>
        public BigXmlDocument() {
            this.reader = new BigXmlReader();
        }

        /// <summary>
        /// Initializes a new instance of the BigXmlDocument class.
        /// </summary>
        /// <param name="path">Path to the xml document.</param>
        public BigXmlDocument(string path) {
            //Contract.Requires<ArgumentNullException>(!string.IsNullOrEmpty(path), "path");

            this.path = path;
            this.reader = new BigXmlReader(path);
        }
        #endregion

        #region Properties
        #region Public
        /// <summary>
        /// Gets or sets the root element.
        /// </summary>
        public BigXmlNode Root {
            get {
                if(this.rootRemoved) {
                    return null;
                }

                return this.root ?? (this.root = this.GetRootElement());
            }

            set {
                if(value != null) {
                    this.rootRemoved = false;
                }

                this.root = value as BigXmlNodeInternal;
            }
        }

        /// <summary>
        /// Gets the <see cref="IBigXmlReader"/> used to read the xml file.
        /// </summary>
        public IBigXmlReader Reader {
            get { return this.XmlReader; }
        }
        #endregion

        #region Private
        private BigXmlReader XmlReader {
            get {
                if(this.reader == null && !string.IsNullOrEmpty(this.path)) {
                    this.reader = new BigXmlReader(this.path);
                    return this.reader;
                }

                return this.reader;
            }
        }
        #endregion
        #endregion

        #region Methods
        #region Public
        public void RemoveRootNode() {
            this.rootRemoved = true;
            this.Root = null;
        }

        /// <summary>
        /// Gets the document type of the document if exists.
        /// </summary>
        /// <returns><see cref="XmlDocumentType"/> for current document or null if there is no doc type.</returns>
        public XmlDocumentType GetDocumentType() {
            return this.Reader.GetDocumentType();
        }

        /// <summary>
        /// Gets child nodes of the specified parent node.
        /// </summary>
        /// <param name="parent">Parent node to get the child nodes of.</param>
        /// <param name="range">Range of child nodes to get. Specify empty string to get all the child nodes.</param>
        /// <returns>Collection of BigXmlNodes.</returns>
        public IEnumerable<BigXmlNodeInternal> GetChildNodes(BigXmlNodeInternal parent, string range) {
            var childNodes = this.GetChildNodes(parent.Address, range);

            foreach(var child in childNodes) {
                parent.XmlNode.AppendChild(child.XmlNode);
                child.SetParent(parent);
            }

            return childNodes;
        }

        /// <summary>
        /// Gets the collection of child nodes for the specified parent node and range.
        /// </summary>
        /// <param name="parent">Parent node of which to get the child nodes.</param>
        /// <param name="range">Range of child nodes to get.</param>
        /// <returns>Collection of <see cref="BigXmlNode"/> instances.</returns>
        public IEnumerable<BigXmlNodeInternal> GetChildNodes(BigXmlNodeInternal parent, IEnumerable<int> range) {
            var address = parent.Address;
            if(parent.OriginalAddress != null && parent.Address.SequenceEqual(parent.OriginalAddress)) {
                address = parent.Address;
            } else if(parent.OriginalAddress != null && !parent.Address.SequenceEqual(parent.OriginalAddress)) {
                address = parent.OriginalAddress;
            }

            var childNodes = this.Reader.GetChildNodes(address, range, parent.TotalChildCount);

            foreach(var child in childNodes.Where(child => child != null)) {
                parent.XmlNode.AppendChild(child.XmlNode);
                child.SetParent(parent);
            }

            return childNodes;
        }

        /// <summary>
        /// Gets child nodes of the node at the specified address.
        /// </summary>
        /// <param name="address">Address of the parent node which child nodes to get.</param>
        /// <param name="range">Range of child nodes to get. Specify empty string to get all child nodes.</param>
        /// <returns>Collection of <see cref="BigXmlNode"/>.</returns>
        public IEnumerable<BigXmlNodeInternal> GetChildNodes(int[] address, string range) {
            var childNodes = this.reader.GetChildNodes(address, range);

            return childNodes;
        }

        /// <summary>
        /// Writes document type to the <see cref="XmlWriter"/>.
        /// </summary>
        /// <param name="documentType">Document type to write.</param>
        /// <param name="writer">Destination where to write.</param>
        public void WriteXmlDocumentType(XmlDocumentType documentType, XmlWriter writer) {
            if(documentType != null) {
                writer.WriteDocType(documentType.Name, documentType.PublicId, documentType.SystemId, documentType.InternalSubset);
            }
        }
        #endregion

        #region Private
        /// <summary>
        /// Gets the document's root element.
        /// </summary>
        /// <returns>Root element as a BigXmlNode instance. If there is no root element, returns null.</returns>
        private BigXmlNodeInternal GetRootElement() {
            return this.XmlReader.GetRootElement();
        }
        #endregion
        #endregion
    }
}